using System;
using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace SlickGridNet
{
	/// <summary>
	/// Base abstract class for SlickGrid options objects (Column Options, Grid Options)
	/// </summary>
	public abstract class OptionsBase: IDictionary<string, object>
	{
		protected Dictionary<string, object> defaultValues = new Dictionary<string, object>();
		protected Dictionary<string, object> values = new Dictionary<string, object>();

		/// <summary>
		/// Set value of specific property in internal collection.
		/// <para>If value of property equals to default value it won't be set and will be cleared from internal collection.</para>
		/// </summary>
		/// <param name="property">Name of the property to set value.</param>
		/// <param name="value">Value of property.</param>
		protected virtual void Set(string property, object value)
		{
			if (property == null)
				throw new ArgumentNullException("property", "Property name can't be null.");

			// If we already has that value as default, we needn't set it
			if (defaultValues.ContainsKey(property) && defaultValues[property] == value)
			{
				values.Remove(property);
			}
			else
			{
				values[property] = value;
			}
		}

		/// <summary>
		/// Returns value of property with specified name.
		/// </summary>
		/// <param name="property">Name of property.</param>
		/// <returns>Value of property.
		/// <para>If property not set default value will be returned, if present.</para>
		/// <para>Else <c>null</c> will be returned.</para>
		/// </returns>
		protected virtual object Get(string property)
		{
			if (values.ContainsKey(property))
				return values[property];
			if (defaultValues.ContainsKey(property))
				return defaultValues[property];
			return null;
		}


		/// <summary>
		/// Adds property with specific value.
		/// </summary>
		/// <param name="property">Name of the property.</param>
		/// <param name="value">Value of property.</param>
		/// <returns>Returns current instance of OptionsBase.</returns>
		/// <exception cref="ArgumentException">Thrown if such property already exists.</exception>
		public OptionsBase AddProperty(string property, object value)
		{
			if (defaultValues.ContainsKey(property))
				throw new ArgumentException("Property with that name already exists", "property");

			Set(property, value);

			return this;
		}
		
		internal protected void RenderInternal(JsonTextWriter jtw, JsonSerializer serializer)
		{
			serializer.Serialize(jtw, values);
		}


		#region IDictionary<string, object> implementation

		public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
		{
			return values.GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return ((IEnumerable) values).GetEnumerator();
		}

		public void Add(KeyValuePair<string, object> item)
		{
			Add(item.Key, item.Value);
		}

		public void Clear()
		{
			values.Clear();
		}

		public bool Contains(KeyValuePair<string, object> item)
		{
			return values.ContainsKey(item.Key) && values[item.Key] == item.Value;
		}

		public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
		{
			throw new NotImplementedException();
		}

		public bool Remove(KeyValuePair<string, object> item)
		{
			return values.Remove(item.Key);
		}

		public int Count
		{
			get { return values.Count; }
		}

		public bool IsReadOnly
		{
			get { return true; }
		}

		public bool ContainsKey(string key)
		{
			return values.ContainsKey(key);
		}

		public void Add(string key, object value)
		{
			Set(key,value);
		}

		public bool Remove(string key)
		{
			return values.Remove(key);
		}

		public bool TryGetValue(string key, out object value)
		{
			return values.TryGetValue(key, out value);
		}

		public object this[string key]
		{
			get { return Get(key); }
			set { Set(key, value); }
		}

		public ICollection<string> Keys
		{
			get { return values.Keys; }
		}

		public ICollection<object> Values
		{
			get { return values.Values; }
		}

		#endregion
	}
}